/**
 * \file    symbol.h
 * \brief   Parser generator.
 * \author  Jérôme LAURENS
 * \version 0.1
 * \date    September 17, 2014
 *
 * Reads a LALR(1) grammar, generates a parser based on a template file.
 * This is based on lemon parser generator, but has strongly been
 * revisited.
 *
 */

#ifndef SYMBOL_H
#define SYMBOL_H

#include "common.h"
#include "description.h"
#include "rule.h"

LPP_NAMESPACE_BEGIN

class Symbol {
public:
    typedef Symbol __self;

    static SymbolRef Make(Index const id
                          , Grammar & __g
                          , const String & __n
                          , const String & __s="") {
        DEBUG_MESSAGE("NEW SYMBOL: %lu,%s",id,__n.c_str());
        return std::make_shared<__self>(id, __g, __n, __s);
    }

    struct Type {
        enum Value {
            TERMINAL,
            UNDEFINED,
            ACCEPT,
            NONTERMINAL,
            MULTITERMINAL,
            ENDOFFILE,
            ERROR,
            DEFAULT
        };
    };
    struct Precedence {
        enum Value {
            UNKNOWN = 0
        };
    };
    struct Associativity {
        enum Value {
            UNKNOWN,
            LEFT,
            RIGHT,
            NONE
        };
    };
    Index id() const {/*! unique id for this symbol, set at creation time. */
        return id_m;
    };
    Index index() const { /*! Index number for this symbol, defines some ordering. */
        return index_m;
    }
    void index(Index const index) { /*! set the index of the receiver. */
        index_m = index;
    }

    Grammar const & grammar() const { /*! the grammar owning the symbol */
        return grammar_m;
    }
    String const & name() const {/*! Name of the symbol. */
        return name_m;
    }
    String const & shortcut() const {/*! Shortcut of the symbol, used for terminals only. */
        return shortcut_m;
    }
    bool has_shortcut() const {
        return is_terminal() && shortcut_m.size()>0;
    }
    String const & short_name() const {/*! Shortcut for terminals, name otherwise. */
        return has_shortcut()? shortcut_m:name_m;
    }
    Type::Value type() const {/*! Type of the symbol. */
        return type_m;
    }
    Index number_of_rules() const {
        return rules_m.size();
    }
    RuleRefList const get_rules() const {/*! list of rules of this, when non terminal. The callee is the owner. */
        RuleRefList L;
        for (RuleWRef const & r: rules_m) {
            RuleRef R = r.lock();
            L.push_back(R);
            if(!L.back()) {
                throw std::logic_error("Rule no longer exists.");
            }
        }
        std::sort(L.begin(),L.end(),[](RuleRef const & lhs, RuleRef const & rhs)->bool{
            return lhs->id()<rhs->id();
        });
        return L;
    }
    bool add_rule(RuleRef const & R) {
        if (is_non_terminal()) {
            if(!R) {
                throw std::logic_error("Rule do not exist.");
            }
            return rules_m.insert(RuleWRef(R)).second;
        }
        return false;
    }

    SymbolRef const fallback() const {       /*! get fallback token in case this token doesn't parse */
        return fallback_m.lock();
    }
    void fallback(SymbolRef const & symbol) {  /*! set fallback token in case this token doesn't parse */
        fallback_m = symbol;
    }

    Index precedence() const {/*! Precedence if defined (Precedence::UNKNOWN otherwise) */
        return precedence_m;
    }
    void precedence(Index const precedence) {/*! Precedence setter. */
        precedence_m = precedence;
    }
    bool has_precedence() const {
        return precedence_m!=Precedence::UNKNOWN;
    }

    Associativity::Value associativity() const {/*! Associativity if precedence is defined, Associativity::UNKNOWN otherwise. */
        return has_precedence()? associativity_m: Associativity::UNKNOWN;
    }
    void associativity(Associativity::Value const associativity) {/*! Associativity setter. */
        associativity_m = associativity;
    }

    SymbolIndex number_of_sub_symbols() const {
        return sub_symbols_m.size();
    }
    SymbolRefList const get_sub_symbols() const {
        SymbolRefList L;
        for (auto const & x: sub_symbols_m) {
            L.push_back(x.lock());
            if(!L.back()) {
                throw std::logic_error("Sub symbol no longer exists.");
            }
        }
        return L;
    }
    SymbolRef const sub_symbol_at(SymbolIndex const i) const {
        if (i>=number_of_sub_symbols()) {
            throw std::out_of_range((Format("Unexpected %llu≥%llu")%i%number_of_sub_symbols()).str());
        }
        auto itor = sub_symbols_m.begin();
        std::advance(itor,i);
        return itor->lock();
    }
    bool add_sub_symbol(SymbolRef const & symbol) {
        promote_to_multi_terminal();
        return sub_symbols_m.insert(SymbolWRef(symbol)).second;
    }

    SymbolRefList const get_first_set() const {/*! first set, the callee is the owner. */
        SymbolRefList L;
        for (SymbolWRef const & S: first_set_m) {
            L.push_back(S.lock());
            if(!L.back()) {
                throw std::logic_error("First set symbol no longer exists.");
            }
        }
        return L;
    }
    bool  add_to_first_set(SymbolRef const & S) {
        if(!S) {
            throw std::logic_error("Unexpected add_to_first_set void symbol.");
        }
        return first_set_m.insert(SymbolWRef(S)).second;
    }
    bool  add_to_first_set(SymbolRefList const & Ss) {
        bool out = false;
        for (auto const & S: Ss) {
            out = add_to_first_set(S) || out;
        }
        return out;
    }

    Symbol(Index const id
           , Grammar & __g
           , const String & __n
           , const String & __s="")
        : id_m(id)
        , index_m(id)
        , grammar_m(__g)
        , name_m(__n)
        , shortcut_m(__s)
        , precedence_m(Precedence::UNKNOWN)
        , associativity_m(Associativity::UNKNOWN)
        , is_nullable_m(false)
        , is_rhs_m(false) {
        if (__n[0]>='A' && __n[0]<='Z') {
            type_m = Type::TERMINAL;
        } else if (__n[0]>='a' && __n[0]<='z') {
            type_m = Type::NONTERMINAL;
        } else if (__n=="_accept") {
            type_m = Type::ACCEPT;
        } else if (__n[0]=='_') {
            type_m = Type::NONTERMINAL;
        } else if (__n=="$") {
            type_m = Type::ENDOFFILE;
        } else if (__n=="!") {
            type_m = Type::ERROR;
        } else if (__n=="{default}") {
            type_m = Type::DEFAULT;
        } else {
            type_m = Type::UNDEFINED;
        }
        // DEBUG_MESSAGE("+Symbol(%#lx): %s, %lu",reinterpret_cast<std::uintptr_t>(this), name_m.c_str(), id);
    };

    Symbol(Index const id
           , Grammar & __g
           , String const & __n
           , Type::Value const __t
           , String const & __s="")
        : id_m(id)
        , index_m(id)
        , grammar_m(__g)
        , name_m(__n)
        , shortcut_m(__s)
        , type_m(__t)
        , precedence_m(Precedence::UNKNOWN)
        , associativity_m(Associativity::UNKNOWN)
        , is_nullable_m(false)
        , is_rhs_m(false) {
        // DEBUG_MESSAGE("+Symbol(%#lx): %s, %lu",reinterpret_cast<std::uintptr_t>(this), name_m.c_str(), id);
    };

    ~Symbol() {
        // DEBUG_MESSAGE("~Symbol(%#lx): %s",reinterpret_cast<std::uintptr_t>(this), name_m.c_str());
    }

    bool can_reduce() const {/*! true for lookahead that can cause a reduction. What for ?*/
        return is_terminal()||is_error();
    }
    bool is_symbol() const {
        return is_terminal()||is_non_terminal()||is_error();// R U SURE ?
    }
    bool is_terminal() const {
        return type_m==Type::TERMINAL||is_end_of_file();
    }
    bool is_undefined() const {
        return type_m==Type::UNDEFINED;
    }
    bool is_accept() const {
        return type_m==Type::ACCEPT;
    }
    bool is_non_terminal() const {
        return type_m==Type::NONTERMINAL || is_accept() || is_error();
    }
    bool is_multi_terminal() const {
        return type_m==Type::MULTITERMINAL;
    }
    bool is_end_of_file() const {
        return type_m==Type::ENDOFFILE;
    }
    bool is_error() const {
        return type_m==Type::ERROR;
    }
    bool is_default() const {
        return type_m==Type::DEFAULT;
    }
    bool promote_to_default();
    bool is_nullable() const {
        return is_nullable_m && is_non_terminal();
    }
    void is_nullable(const bool yorn) {
        is_nullable_m = yorn;
    }
    bool promote_to_multi_terminal();

private:
    typedef std::set<RuleWRef> RuleWRefSet;
    RuleWRefSet rules_m;       /* list of rules of this (if an NT) */
    typedef std::set<SymbolWRef> SymbolSet;
    SymbolSet sub_symbols_m;  /* Array of constituent symbols */
    SymbolSet first_set_m;     /* first set */
    Index id_m;               /* id for this symbol */
    Index index_m;               /* Index number for this symbol */
    SymbolWRef fallback_m;      /* fallback token in case this token doesn't parse */
    Grammar & grammar_m;
    String const name_m;        /* Name of the symbol */
    String const shortcut_m;    /* Shortcut of the symbol, only for terminals */
    Type::Value type_m;
    Index precedence_m;   /* Precedence if defined (UNKNOWN otherwise) */
    Associativity::Value associativity_m;      /* Associativity if precedence is defined */
    Symbol();
public:
    bool is_rhs() const {
        return is_rhs_m;
    }
    void is_rhs(bool const yorn) {
        is_rhs_m = yorn;
    }
private:
    bool is_nullable_m;             /* True if NT and can generate an empty string */
    bool is_rhs_m;
};

LPP_NAMESPACE_END

#endif // SYMBOL_H
